using System;
using System.Collections.Generic;
using System.Linq;
using PingPong.Generic.Helpers;
using PingPong.Generic;

namespace PingPong.GameEngine.Components
{

	public class Score
	{

		#region Members

		/// <summary>
		/// The score history.
		/// </summary>
		private Stack<int> history;

		/// <summary>
		/// The score value.
		/// </summary>
		private Lazy<int> value;

		#endregion

		#region Properties

		/// <summary>
		/// Gets the score value.
		/// </summary>
		/// <value>The value.</value>
		public int Value
		{
			get
			{
				return this.value.Value;
			}
			private set
			{
				history.Push(this.Value);
				this.value = new Lazy<int>(() => value);
			}
		}

		/// <summary>
		/// Gets the increment expression.
		/// </summary>
		/// <value>The increment expression.</value>
		public string IncrementExpression { get; private set; }

		/// <summary>
		/// Gets the decrement expression.
		/// </summary>
		/// <value>The decrement expression.</value>
		public string DecrementExpression { get; private set; }

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="PingPong.GameEngine.Components.Score"/> class.
		/// </summary>
		public Score()
		{
			this.history = new Stack<int>();
			this.value = new Lazy<int>(() => default(int));
		}

		#endregion

		#region Methods

		#region Events

		/// <summary>
		/// Fires the on score changing.
		/// </summary>
		/// <returns>The on score changing.</returns>
		/// <param name="newValue">New value.</param>
		public Score FireOnScoreChanging(int newValue)
		{
			EventHelper.SafeFireEvent(OnScoreChanging,this,new ValueChangeEventArgs<int>(this.Value, newValue));
			return this;
		}

		/// <summary>
		/// Fires the on score changed.
		/// </summary>
		/// <returns>The on score changed.</returns>
		/// <param name="oldValue">Old value.</param>
		public Score FireOnScoreChanged(int oldValue)
		{
			EventHelper.SafeFireEvent(OnScoreChanged,this,new ValueChangeEventArgs<int>(oldValue, this.Value));
			return this;
		}

		#endregion

		/// <summary>
		/// Sets the increment expression.
		/// </summary>
		/// <returns>The increment expression.</returns>
		/// <param name="expression">Expression.</param>
		public Score SetIncrementExpression(string expression)
		{
			if (string.IsNullOrWhiteSpace(expression))
			{
				throw new ArgumentNullException("expression");
			}
			
			this.IncrementExpression = expression;
			return this;
		}

		/// <summary>
		/// Sets the decrement expression.
		/// </summary>
		/// <returns>The decrement expression.</returns>
		/// <param name="expression">Expression.</param>
		public Score SetDecrementExpression(string expression)
		{
			this.DecrementExpression = expression;
			return this;
		}

		/// <summary>
		/// Sets the score.
		/// </summary>
		/// <returns>The score.</returns>
		/// <param name="expression">Expression.</param>
		public Score SetScore(string expression)
		{
			if (string.IsNullOrWhiteSpace(expression))
			{
				throw new ArgumentNullException("expression");
			}

			return ChangeScore(
				() =>
				{
					Engine.ExpressionParser.Interpret(expression);
					return Engine.ExpressionParser.Int;
				},
				() => this.Value,
				(n, o) => this.Value = n
			);
		}

		/// <summary>
		/// Sets the score.
		/// </summary>
		/// <returns>The score.</returns>
		/// <param name="score">Score.</param>
		public Score SetScore(int score)
		{
			return ChangeScore(() => score,() => this.Value,(n, o) => this.Value = n);
		}

		/// <summary>
		/// Increment this instance.
		/// </summary>
		public Score Increment()
		{
			if (string.IsNullOrWhiteSpace(this.IncrementExpression))
			{
				throw new NotImplementedException("No increment expression set.");
			}

			return ChangeScore(
				() =>
				{
					Engine.ExpressionParser.Interpret(this.IncrementExpression);
					return Engine.ExpressionParser.Int;
				},
				() => this.Value,
				(n, o) => this.Value += n
			);
		}

		/// <summary>
		/// Decrement this instance.
		/// </summary>
		public Score Decrement()
		{
			if (string.IsNullOrWhiteSpace(this.DecrementExpression))
			{
				Reverse();
				return this;
			}
			
			return ChangeScore(
				() =>
				{
					Engine.ExpressionParser.Interpret(this.DecrementExpression);
					return Engine.ExpressionParser.Int;
				}, 
				() => this.Value,
				(n, o) => this.Value += n
			);
		}

		/// <summary>
		/// Reverse this instance.
		/// </summary>
		public Score Reverse()
		{
			if (!this.history.Any())
			{
				throw new ArgumentOutOfRangeException("No history found.");
			}

			return ChangeScore(() => this.history.Pop(),() => this.Value,(n, o) => this.value = new Lazy<int>(() => n));
		}

		public Score ChangeScore(Func<int> newValueSelector, Func<int> oldValueSelector, Action<int, int> valueApplicator)
		{
			Engine.ExpressionParser.Interpret(this.DecrementExpression);
			var newValue = newValueSelector();
			var oldValue = oldValueSelector();

			FireOnScoreChanging(newValue);

			valueApplicator(newValue,oldValue);

			FireOnScoreChanged(oldValue);

			return this;
		}

		#endregion

		#region Events

		/// <summary>
		/// Occurs before the score changes.
		/// </summary>
		public event EventHandler<ValueChangeEventArgs<int>> OnScoreChanging;

		/// <summary>
		/// Occurs after the score has changed.
		/// </summary>
		public event EventHandler<ValueChangeEventArgs<int>> OnScoreChanged;

		#endregion

		#region Operators

		/// <param name="score">Score.</param>
		public static implicit operator int(Score score)
		{
			return score.Value;
		}

		#endregion

	}
}

